﻿using System;
using System.Collections.Generic;
using Arroguella.Entities;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Arroguella.Tiles;
using System.Globalization;
using Arroguella.Utilities;
using System.Diagnostics;

namespace Arroguella
{
    public class Player
    {
        #region Constructor
        #endregion

        #region Events

        #region Death

        public class DeathEventArgs : EventArgs
        {
            // TODO
        }

        public delegate void DeathHandler(object sender, DeathEventArgs e);

        public event DeathHandler Death;

        /// <summary>
        /// Triggers the Death event
        /// </summary>
        public void OnDeath()
        {
            DeathHandler death = Death;
            if (death != null)
            {
                death(this, new DeathEventArgs());
            }
        }

        #endregion

        #region Levelling Up

        public class LevelUpEventArgs : EventArgs
        {
            public LevelUpEventArgs(int level)
            {
                Level = level;
            }

            public int Level { get; set; }
        }

        public delegate void LevelUpHandler(object sender, LevelUpEventArgs e);

        public event LevelUpHandler LevelUp;

        /// <summary>
        /// Increases the Level, resets XP to 0,
        /// then triggers the LevelUp event.
        /// </summary>
        public void OnLevelUp()
        {
            int extra = xp - XpForNextLevel;
            Level++;
            XP = 0 + extra;

            LevelUpHandler levelUp = LevelUp;
            if (levelUp != null)
            {
                levelUp(this, new LevelUpEventArgs(Level));
            }
        }

        #endregion        

        #endregion

        #region Methods

        /// <summary>
        /// Initializes the player.
        /// </summary>
        public void Initialize(SpriteFont font, Vector2 tileCoords, Rectangle mapArea)
        {
            Font = font;

            Color = Color.Gainsboro;

            // Set up the character
            Level = 1;
            Strength = 3;
            Agility = 3;
            Intelligence = 3;
            MaxHealth = 10;
            CurrentHealth = 10;
            MaxMana = 5;
            CurrentMana = 5;
            Money = 3;

            // Skills
            skills.Add("Melee Accuracy", 0f);
            skills.Add("Ranged Accuracy", 0f);
            skills.Add("Dodge", 0f);
            skills.Add("Dungeoneering", 0f);

            // Initialize Inventory
            Inventory = new Inventory(this);
            EquipmentSlots = new Dictionary<string, Entity>();

            // Set the starting position of the player in pixels
            MapArea = mapArea;
            position = new Vector2(MapArea.Left + tileCoords.X * Tile.Size.X,
                MapArea.Top + tileCoords.Y * Tile.Size.Y);
            Coords = tileCoords;
            TileBoundaries = new Rectangle(MapArea.Left + (int)(tileCoords.X * Tile.Size.X), MapArea.Top + (int)(tileCoords.Y * Tile.Size.Y), (int)Tile.Size.X, (int)Tile.Size.Y);

            // FOV
            FovRadius = 5;
            LightIntensity = 0.035f;
        }

        /// <summary>
        /// Things that occur during the Update phase.
        /// </summary>
        public void Update()
        {
        }

        /// <summary>
        /// Moves the Player's position in Tile units.
        /// </summary>
        /// <param name="dx">The number of Tiles to move in the X-direction. Negative is West, Positive is East.</param>
        /// <param name="dy">The number of Tiles to move in the Y-direction. Negative is North, Positive is South.</param>
        public void MoveTiles(int dx, int dy)
        {
            int xOffset = (int)(dx * Tile.Size.X);
            int yOffset = (int)(dy * Tile.Size.Y);

            position.X += xOffset;
            position.Y += yOffset;

            Coords.X += dx;
            Coords.Y += dy;

            TileBoundaries = new Rectangle((int)position.X, (int)position.Y, (int)Tile.Size.X, (int)Tile.Size.Y);
        }

        /// <summary>
        /// Determines how the player is drawn.
        /// </summary>
        public void Draw(SpriteBatch spriteBatch)
        {
            Utils.DrawString(spriteBatch, Font, "@", Color, TileBoundaries);
        }

        /// <summary>
        /// Gets a string representing the value of the requested property.
        /// For instance, player.GetProperty("Level") will return the player's level as a string.
        /// </summary>
        /// <param name="propertyName">The name of the property to check.</param>
        public string GetProperty(string propertyName)
        {
            propertyName = propertyName.ToUpperInvariant();
            switch (propertyName)
            {
                case "LEVEL":
                    return Level.ToString();
                case "XP":
                    return XP.ToString();
                case "XpForNextLevel":
                    return XpForNextLevel.ToString();
                case "STR":
                case "STRENGTH":
                    return Strength.ToString();
                case "AGI":
                case "AGILITY":
                    return Agility.ToString();
                case "INT":
                case "INTELLIGENCE":
                    return Intelligence.ToString();
                case "MAXHP":
                case "MAXHEALTH":
                    return MaxHealth.ToString();
                case "HP":
                case "CURRENTHP":
                case "HEALTH":
                case "CURRENTHEALTH":
                    return CurrentHealth.ToString();
                case "MAXMANA":
                    return MaxMana.ToString();
                case "MANA":
                case "CURRENTMANA":
                    return CurrentMana.ToString();
                case "GP":
                case "GOLD":
                case "MONEY":
                    return Money.ToString("0,0.0", CultureInfo.InvariantCulture) + "GP";
                case "PP":
                case "PLATINUM":
                    return (Money / 10).ToString("0,0.0", CultureInfo.InvariantCulture) + "PP";
                case "SP":
                case "SILVER":
                    return (Money * 10).ToString("0,0.0", CultureInfo.InvariantCulture) + "SP";
                case "CP":
                case "COPPER":
                    return (Money * 100).ToString("0,0.0", CultureInfo.InvariantCulture) + "CP";
                default:
                    Console.WriteLine("Unknown property requested from Player object: " + propertyName);
                    return string.Empty;
            }
        }

        /// <summary>
        /// A brief description of the player.
        /// </summary>
        public override string ToString()
        {
            return string.Format("Player, Level {0} XP: {9}/{10}. STR: {1} AGI: {2} INT: {3} HP: {4}/{5} Mana: {6}/{7} Gold: {8}",
                Level, Strength, Agility, Intelligence, CurrentHealth, MaxHealth, CurrentMana, MaxMana, GetProperty("GP"), XP, XpForNextLevel);
        }

        #endregion

        #region Inventory

        #region Iventory Methods

        /// <summary>
        /// Gets a certain entity and adds it to the player's inventory.
        /// (As long as the player can carry it!)
        /// </summary>
        public bool Get(Entity entity)
        {
            return Inventory.Get(entity);
        }

        /// <summary>
        /// Gets all the entities from a tile and adds it to the player's inventory.
        /// (As long as the player can carry them!)
        /// </summary>
        public bool GetAll(Tile tile)
        {
            return Inventory.GetAll(tile);
        }

        /// <summary>
        /// Drops an entity from the player's inventory.
        /// If the entity was equipped, it is automatically unequipped.
        /// </summary>
        public Entity Drop(string entityName)
        {
            Entity entity;
            TryGetEntity(entityName, out entity);
            if (entity != null && entity is EquipmentEntity)
            {
                Unequip(entity as EquipmentEntity);
            }

            return Inventory.Drop(entityName);
        }

        /// <summary>
        /// Drops an entity from the player's inventory.
        /// </summary>
        public void Drop(Entity entity)
        {
            Inventory.Drop(entity);
        }

        /// <summary>
        /// Attempts to add an Entity to the Inventory.
        /// If this type of Entity already exists, we increase its quantity.
        /// </summary>
        /// <param name="entity">The entity to be added</param>
        /// <returns>True if we were successful, false otherwise.</returns>
        internal bool AddToInventory(Entity entity)
        {
            return Inventory.AddToInventory(entity);
        }

        /// <summary>
        /// Tries to get a reference to the Entity in Inventory.
        /// This does NOT remove the Entity from Inventory.
        /// </summary>
        public void TryGetEntity(string name, out Entity entity)
        {
            Inventory.TryGetEntity(name, out entity);
        }

        /// <summary>
        /// Removes an Entity from Inventory.
        /// If this reduces that Entity's quantity to 0,
        /// we remove all references to it here. 
        /// </summary>
        /// <param name="name">The name of the Entity to be removed.</param>
        /// <returns>The Entity specified.</returns>
        internal Entity RemoveFromInventory(string name)
        {
            Entity entity;
            TryGetEntity(name, out entity);
            if (entity != null && entity is EquipmentEntity)
            {
                Unequip(entity as EquipmentEntity);
            }

            return Inventory.RemoveFromInventory(name);
        }

        /// <summary>
        /// If the entity is equipped, unequip it.
        /// Otherwise, equip it.
        /// </summary>
        public void ToggleEquip(EquipmentEntity entity)
        {
            if (entity.IsEquipped)
            {
                Unequip(entity);
            }
            else
            {
                Equip(entity);
            }
        }

        /// <summary>
        /// Equips the entity to the first available slot, if possible.
        /// </summary>
        internal void Equip(EquipmentEntity entity)
        {
            foreach (string slot in entity.SlotsEquippable)
            {
                if (!EquipmentSlots.ContainsKey(slot))
                {
                    EquipmentSlots.Add(slot, entity);
                    entity.EquippedSlot = slot;
                    Debug.WriteLine(string.Format("Equipped {0} to {1}", entity.Name, slot));
                    return;
                }
            }
        }

        /// <summary>
        /// Unequips the entity from its slot, if possible.
        /// </summary>
        /// <param name="entity"></param>
        internal void Unequip(EquipmentEntity entity)
        {
            string slot = entity.EquippedSlot;

            if (string.IsNullOrEmpty(slot))
            {
                return;
            }

            if(EquipmentSlots.ContainsKey(slot))
            {                
                EquipmentSlots.Remove(slot);
                Debug.WriteLine(string.Format("UnEquipped {0} from {1}", entity.Name, slot));
            }
            entity.EquippedSlot = string.Empty;
        }

        #endregion

        #region Inventory Properties

        public Inventory Inventory;

        /// <summary>
        /// Equipment slots for the player. This ensures that the player cannot equip more than one item per slot.
        /// For instance, the player might equip the SWORD entity to the MAIN HAND slot, or the HELMET entity to the HEAD slot.
        /// </summary>
        public Dictionary<string, Entity> EquipmentSlots;

        /// <summary>
        /// The amount of wieght (in kg) that the player is currently holding.
        /// </summary>
        public float CurrentCarriedWeight { get; set; }

        /// <summary>
        /// The amount of weight (in kg) that the player can hold,
        /// both by itself and within the various storage items it carries.
        /// </summary>
        public float CarryingCapacity
        {
            get
            {
                float carryingCapacity = 5*Strength;

                foreach (KeyValuePair<string, Entity> entry in Inventory.EntityNames)
                {
                    int quantity = Inventory.EntityQuantities[entry.Key];
                    Entity entity = entry.Value;

                    StorageEntityComponent storage = entity.Storage;
                    if (storage != null)
                    {
                        carryingCapacity += quantity*storage.WeightLimit;
                    }
                }

                return carryingCapacity;
            }
        }

        /// <summary>
        /// If we are wielding a weapon in our main hand, it is our primary weapon.
        /// </summary>
        public EquipmentEntity PrimaryWeapon
        {
            get
            {
                if (EquipmentSlots.ContainsKey("Main Hand"))
                {
                    var primarySlot = EquipmentSlots["Main Hand"];
                    if (primarySlot is EquipmentEntity)
                    {
                        return primarySlot as EquipmentEntity;
                    }
                }

                if (EquipmentSlots.ContainsKey("Off Hand"))
                {
                    var secondarySlot = EquipmentSlots["Off Hand"];
                    if (secondarySlot is EquipmentEntity)
                    {
                        return secondarySlot as EquipmentEntity;
                    }
                }                

                return null;
            }
        }

        /// <summary>
        /// If we have a primary weapon, and are wielding another weapon in our other hand, it is the secondary weapon.
        /// </summary>
        public EquipmentEntity SecondaryWeapon
        {
            get
            {
                if (EquipmentSlots.ContainsKey("Main Hand") && EquipmentSlots.ContainsKey("Off Hand"))
                {
                    var primarySlot = EquipmentSlots["Main Hand"];
                    if (primarySlot is EquipmentEntity)
                    {
                        var secondarySlot = EquipmentSlots["Off Hand"];
                        if (secondarySlot is EquipmentEntity)
                        {
                            return secondarySlot as EquipmentEntity;
                        }
                    }
                }                

                return null;
            }
        }

        /// <summary>
        /// The total modifer to accuracy based on the player's equipped gear and items.
        /// </summary>
        public float AccuracyModifierFromEquipment
        {
            get
            {
                int mod = 0;
                foreach (var slot in EquipmentSlots)
                {
                    if (slot.Value is EquipmentEntity)
                    {
                        mod += (slot.Value as EquipmentEntity).AccuracyModifier;
                    }
                }

                return mod;
            }
        }

        /// <summary>
        /// The total modifer to damage based on the player's equipped gear and items.
        /// </summary>
        public int DamageModifierFromEquipment
        {
            get
            {
                int mod = 0;
                foreach (var slot in EquipmentSlots)
                {
                    if (slot.Value is EquipmentEntity)
                    {
                        mod += (slot.Value as EquipmentEntity).DamageModifier;
                    }
                }

                return mod;
            }
        }

        /// <summary>
        /// The total modifer to defense based on the player's equipped gear and items.
        /// </summary>
        public float DefenseModifierFromEquipment
        {
            get
            {
                int mod = 0;
                foreach (var slot in EquipmentSlots)
                {
                    if (slot.Value is EquipmentEntity)
                    {
                        mod += (slot.Value as EquipmentEntity).DefenseModifier;
                    }
                }

                return mod;
            }
        }

        #endregion

        #endregion

        #region Properties

        #region Character Properties

        /// <summary>
        /// The character level.
        /// </summary>
        public int Level { get; set; }

        private int xp;
        /// <summary>
        /// The current number of experience points (XP).
        /// This resets to 0 at each new level gained.
        /// </summary>
        public int XP
        {
            get { return xp; }
            set
            {
                xp = value;
                if (xp >= XpForNextLevel)
                {
                    OnLevelUp();
                }
            }
        }

        /// <summary>
        /// The total number of experience points required for the next level.
        /// </summary>
        public int XpForNextLevel { get { return Level * 1000; } }

        #region Attributes

        /// <summary>
        /// Character strength, affects damage, health and strength-based skills.
        /// </summary>
        public int Strength { get; set; }

        /// <summary>
        /// Character agility, affects defense, accuracy and agility-based skills.
        /// </summary>
        public int Agility { get; set; }

        /// <summary>
        /// Character intelligence, affects mana and intelligence/knowledge-based skills.
        /// </summary>
        public int Intelligence { get; set; }

        /// <summary>
        /// The total amount of hit points the player can have.
        /// </summary>
        public int MaxHealth { get; set; }

        /// <summary>
        /// The current amount of hit points the player has.
        /// </summary>
        public int CurrentHealth { get; set; }

        /// <summary>
        /// The total amount of mana the player can have.
        /// </summary>
        public int MaxMana { get; set; }

        /// <summary>
        /// The current amount of mana the player has.
        /// </summary>
        public int CurrentMana { get; set; }

        public float Defense { get { return Agility + 10 + Skills["Dodge"] + DefenseModifierFromEquipment; } }

        #endregion

        #region Skills

        private Dictionary<string, float> skills = new Dictionary<string, float>();
        /// <summary>
        /// The player's various skills, indexed by name.
        /// </summary>
        public Dictionary<string, float> Skills { get { return skills; } }

        #endregion

        /// <summary>
        /// The amount of money the player possesses, in Gold Pieces (GP)
        /// 1 GP = 10 Silver Pieces (SP)
        /// 1 SP = 10 Copper Pieces (CP)
        /// </summary>
        public float Money { get; set; }

        #endregion

        public SpriteFont Font;

        /// <summary>
        /// The Tile coordinates of the Player,
        /// relative to the origin tile in the upper left corner.
        /// </summary>
        public Vector2 Coords;

        /// <summary>
        /// Position of the Player relative to the upper left side of the screen, in pixels.
        /// </summary> 
        private Vector2 position;

        public Rectangle TileBoundaries { get; set; }

        /// <summary>
        /// A rectangle defining, in pixels, the area of the map (where the tiles are)
        /// </summary>
        protected Rectangle MapArea;

        /// <summary>
        /// The color of the player. Default is Gainsboro.
        /// </summary>
        public Color Color { get; set; }

        public int FovRadius { get; set; }

        public float LightIntensity { get; set; }

        #endregion
    }
}
